baubs git
Commit 7a1178166a03cc31fc5dd66b92a05fac6c7f492f
Parents : 6048a61
Author : Jan-Henrik Bruhn <hi@jhbruhn.de>
Date : 2025-12-14T16:44:56+01:00
fix: Combine COLOR_END and CUT flags on same stitch for color changes
The COLOR_END and CUT commands must be on the same stitch, not separate stitches.
This was causing the machine to execute an extra stitch with the new color before
the jump to the new position.
Changes:
- Combine COLOR_END (X flag) and CUT (Y flag) into single stitch at old position
- Machine now correctly pauses after cut, before jumping to new color section
- Update all color change tests to expect combined COLOR_END+CUT stitch
The correct sequence is now:
1. Finishing lock stitches (old color)
2. COLOR_END+CUT stitch (old color) ← Machine pauses here
3. Jump to new position (new color)
4. Starting lock stitches (new color)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
2 files changed, 39 insertions(+), 60 deletions(-)
Diff
diff --git a/src/formats/pen/encoder.test.ts b/src/formats/pen/encoder.test.ts
index 21493f9..189be15 100644
--- a/src/formats/pen/encoder.test.ts
+++ b/src/formats/pen/encoder.test.ts
@@ -298,21 +298,15 @@ describe('encodeStitchesToPen', () => {
idx++;
}
- // 4. Cut command
- const cutStitch = decoded[idx];
- expect(cutStitch.x).toBe(10);
- expect(cutStitch.y).toBe(0);
- expect(cutStitch.isCut).toBe(true);
+ // 4. COLOR_END + CUT command (combined on same stitch)
+ const colorEndCutStitch = decoded[idx];
+ expect(colorEndCutStitch.x).toBe(10);
+ expect(colorEndCutStitch.y).toBe(0);
+ expect(colorEndCutStitch.isCut).toBe(true);
+ expect(colorEndCutStitch.isColorEnd).toBe(true);
idx++;
- // 5. COLOR_END marker (no jump needed since same position)
- const colorEndStitch = decoded[idx];
- expect(colorEndStitch.x).toBe(10);
- expect(colorEndStitch.y).toBe(0);
- expect(colorEndStitch.isColorEnd).toBe(true);
- idx++;
-
- // 6. 8 starting lock stitches for new color
+ // 5. 8 starting lock stitches for new color
for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
@@ -320,7 +314,7 @@ describe('encodeStitchesToPen', () => {
idx++;
}
- // 7. First stitch of new color
+ // 6. First stitch of new color
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
idx++;
@@ -353,9 +347,11 @@ describe('encodeStitchesToPen', () => {
idx++;
}
- // 2. Cut command at (10, 0)
- expect(decoded[idx].isCut).toBe(true);
+ // 2. COLOR_END + CUT command at (10, 0) - combined on same stitch
expect(decoded[idx].x).toBe(10);
+ expect(decoded[idx].y).toBe(0);
+ expect(decoded[idx].isCut).toBe(true);
+ expect(decoded[idx].isColorEnd).toBe(true);
idx++;
// 3. Jump to new position (30, 10)
@@ -364,20 +360,14 @@ describe('encodeStitchesToPen', () => {
expect(decoded[idx].isFeed).toBe(true);
idx++;
- // 4. COLOR_END marker at (30, 10)
- expect(decoded[idx].x).toBe(30);
- expect(decoded[idx].y).toBe(10);
- expect(decoded[idx].isColorEnd).toBe(true);
- idx++;
-
- // 5. 8 starting lock stitches at (30, 10)
+ // 4. 8 starting lock stitches at (30, 10)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
idx++;
}
- // 6. Continue with new color stitches
+ // 5. Continue with new color stitches
expect(decoded[idx].x).toBe(30);
expect(decoded[idx].y).toBe(10);
});
@@ -421,10 +411,11 @@ describe('encodeStitchesToPen', () => {
idx++;
}
- // 4. Cut command at (10, 0)
+ // 4. COLOR_END + CUT command at (10, 0) - combined on same stitch
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isCut).toBe(true);
+ expect(decoded[idx].isColorEnd).toBe(true);
idx++;
// 5. Jump to new location (50, 20) - extracted from the MOVE stitch
@@ -433,26 +424,20 @@ describe('encodeStitchesToPen', () => {
expect(decoded[idx].isFeed).toBe(true);
idx++;
- // 6. COLOR_END marker at (50, 20)
- expect(decoded[idx].x).toBe(50);
- expect(decoded[idx].y).toBe(20);
- expect(decoded[idx].isColorEnd).toBe(true);
- idx++;
-
- // 7. 8 starting lock stitches at (50, 20)
+ // 6. 8 starting lock stitches at (50, 20)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
idx++;
}
- // 8. First actual stitch of new color at (50, 20)
+ // 7. First actual stitch of new color at (50, 20)
expect(decoded[idx].x).toBe(50);
expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isFeed).toBe(false);
idx++;
- // 9. Last stitch with DATA_END
+ // 8. Last stitch with DATA_END
expect(decoded[idx].x).toBe(60);
expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isDataEnd).toBe(true);
@@ -666,7 +651,7 @@ describe('encodeStitchesToPen', () => {
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
- console.log(decoded);
+
// Expected sequence:
// 0. Feed move to proper location (should always happen here)
// 1. 8 starting lock stitches at (10, 20)
diff --git a/src/formats/pen/encoder.ts b/src/formats/pen/encoder.ts
index 122ed8d..538cf62 100644
--- a/src/formats/pen/encoder.ts
+++ b/src/formats/pen/encoder.ts
@@ -17,7 +17,7 @@ const PEN_DATA_END = 0x05; // Last stitch of entire pattern
const FEED_LENGTH = 50; // Long jump threshold requiring lock stitches and cut
const TARGET_LENGTH = 8.0; // Target accumulated length for lock stitch direction
const MAX_POINTS = 5; // Maximum points to accumulate for lock stitch direction
-export const LOCK_STITCH_JUMP_SIZE = 4.0;
+export const LOCK_STITCH_JUMP_SIZE = 2.0;
const LOCK_STITCH_SCALE = LOCK_STITCH_JUMP_SIZE / 8.0; // Scale the magnitude-8 vector down to 4
export interface StitchData {
@@ -286,7 +286,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
penStitches.push(...generateLockStitches(absX, absY, startDir.dirX, startDir.dirY));
}
- // Handle color change: finishing lock, cut, jump, COLOR_END, starting lock
+ // Handle color change: finishing lock, COLOR_END+CUT, jump, starting lock
if (isColorChange) {
const nextStitchCmd = nextStitch[2];
const nextStitchX = Math.round(nextStitch[0]);
@@ -299,17 +299,26 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
const finishDir = calculateLockDirection(stitches, i, true);
penStitches.push(...generateLockStitches(absX, absY, finishDir.dirX, finishDir.dirY));
- // Step 2: Add cut command at current position
- const cutXEncoded = (absX << 3) & 0xffff;
- const cutYEncoded = ((absY << 3) & 0xffff) | PEN_CUT_DATA;
+ // Step 2: Add COLOR_END + CUT command at CURRENT position (same stitch!)
+ // This is where the machine pauses and waits for the user to change thread color
+ // IMPORTANT: COLOR_END and CUT must be on the SAME stitch, not separate stitches
+ let colorEndCutXEncoded = (absX << 3) & 0xffff;
+ let colorEndCutYEncoded = (absY << 3) & 0xffff;
+
+ // Add COLOR_END flag to X coordinate and CUT flag to Y coordinate
+ colorEndCutXEncoded = (colorEndCutXEncoded & 0xfff8) | PEN_COLOR_END;
+ colorEndCutYEncoded |= PEN_CUT_DATA;
penStitches.push(
- cutXEncoded & 0xff,
- (cutXEncoded >> 8) & 0xff,
- cutYEncoded & 0xff,
- (cutYEncoded >> 8) & 0xff
+ colorEndCutXEncoded & 0xff,
+ (colorEndCutXEncoded >> 8) & 0xff,
+ colorEndCutYEncoded & 0xff,
+ (colorEndCutYEncoded >> 8) & 0xff
);
+ // Machine pauses here for color change
+ // After user changes color, the following stitches execute with the new color
+
// Step 3: If next stitch is a JUMP, encode it and skip it in the loop
// Otherwise, add a jump ourselves if positions differ
const jumpToX = nextStitchX;
@@ -334,22 +343,7 @@ export function encodeStitchesToPen(stitches: number[][]): PenEncodingResult {
);
}
- // Step 4: Add COLOR_END marker at NEW position
- // This is where the machine pauses and waits for the user to change thread color
- let colorEndXEncoded = (jumpToX << 3) & 0xffff;
- const colorEndYEncoded = (jumpToY << 3) & 0xffff;
-
- // Add COLOR_END flag to X coordinate
- colorEndXEncoded = (colorEndXEncoded & 0xfff8) | PEN_COLOR_END;
-
- penStitches.push(
- colorEndXEncoded & 0xff,
- (colorEndXEncoded >> 8) & 0xff,
- colorEndYEncoded & 0xff,
- (colorEndYEncoded >> 8) & 0xff
- );
-
- // Step 5: Add starting lock stitches at the new position
+ // Step 4: Add starting lock stitches at the new position
// Loop A: Jump/Entry Vector - Look FORWARD at upcoming stitches in new color
// This hides the knot under the stitches we're about to make
const nextStitchIdx = nextIsJump ? i + 2 : i + 1;
Served by rngit 1.4.2 - Generated in 0.04s